#!/usr/bin/env python
#
# Copyright 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""Simple app to demonstrate sending two-legged OAuth requests.

Sends a signed request to http://gaeoauth.appspot.com/verify, and then
returns the response to the browser.
"""

from google.appengine.dist import use_library
use_library('django', '1.1')

import Cookie
import logging
import os
import sys
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
import oauthfetch
import wsgiref.handlers
from django.utils import simplejson


_CONTACTS_URL = (
    'http://www-opensocial.googleusercontent.com/api/people/'
    '@me/@all?fields=displayName,emails')

_REQUEST_TOKEN_URL = (
    'https://www.google.com/accounts/OAuthGetRequestToken?'
    'scope=http://www-opensocial.googleusercontent.com/api/people/')

_APPROVAL_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken'

_ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken'

_STATE_COOKIE = 'contacts_state'


class FetchContactsHandler(webapp.RequestHandler):
  """Fetches the user's Google contacts."""

  def get(self):
    # Never cache these pages.
    _SetNoCache(self.response)

    # Require login
    if users.get_current_user() is None:
      self.redirect(users.create_login_url(self.request.uri))
      return
      
    # Try to fetch the user's contacts
    contacts_response = _FetchContacts()
    if contacts_response.oauth_state is not None:
      _SaveStateCookie(self.response, contacts_response.oauth_state)

    # Display the user's contacts
    _DisplayContacts(self.response, contacts_response)


class FetchContactsCallbackHandler(webapp.RequestHandler):
  """Handles OAuth callback."""
  
  def get(self):
    # Never cache these pages.
    _SetNoCache(self.response)

    # Require login
    if users.get_current_user() is None:
      self.redirect(users.create_login_url(self.request.uri))
      return

    # Try to fetch the user's contacts.  We are using OAuth 1.0a, so we
    # need to pass along the user's callback URL as well.
    contacts_response = _FetchContacts(received_callback=self.request.uri)
    if contacts_response.oauth_state is not None:
      _SaveStateCookie(self.response, contacts_response.oauth_state)

    if contacts_response.oauth_error is None:
      _DisplayApproved(self.response, contacts_response)
    else:
      _DisplayError(self.response, contacts_response)


def _FetchContacts(received_callback=None):
  """Fetch the user's contacts using the oauth proxy."""
  logging.error('Fetching contacts, callback=%s' % received_callback)
  rpc = urlfetch.create_rpc()
  my_callback = 'http://gaeoauth.appspot.com/sign/poco/callback'
  state = _LoadStateCookie()
  logging.error('State cookie is %s' % state)
  oauthfetch.make_fetch_call(
      rpc,
      _CONTACTS_URL,
      oauth_desired_callback_url=my_callback,
      oauth_received_callback_url=received_callback,
      oauth_request_token_url=_REQUEST_TOKEN_URL,
      oauth_authorization_url=_APPROVAL_URL,
      oauth_access_token_url=_ACCESS_TOKEN_URL,
      oauth_state=state,
      oauth_service_name='google',
      oauth_token_name='contacts',
      oauth_use_token='always')
  return oauthfetch.OAuthURLFetchResult(rpc.get_result())


def _SaveStateCookie(response, oauth_state):
  """Use a state cookie to track the user during the approval process."""
  cookies = Cookie.SimpleCookie()
  cookies[_STATE_COOKIE] = oauth_state
  cookies[_STATE_COOKIE]['path'] = '/sign/poco'
  response.headers.add_header('Set-Cookie',
                              cookies[_STATE_COOKIE].OutputString())


def _LoadStateCookie():
  """Read the state cookie."""
  cookies = Cookie.BaseCookie()
  cookies.load(os.environ.get('HTTP_COOKIE', None))
  state = cookies.get(_STATE_COOKIE)
  if state is not None:
    return state.value
  return state


def _BuildTemplateArguments(contacts_response):
  """Prepare to write the response into an HTML page."""
  params = {}
  params['logout'] = users.create_logout_url('/')

  if contacts_response.oauth_error:
    # Machine readable error code
    params['error_code'] = contacts_response.oauth_error

  if contacts_response.oauth_error_text:
    # Human readable error code
    params['error_text'] = contacts_response.oauth_error_text

  if contacts_response.oauth_approval_url:
    params['approval_url'] = contacts_response.oauth_approval_url

  try:
    params['contacts'] = _ParsePortableContacts(
        contacts_response.url_fetch_result.content)
  except:
    params['all_data'] = contacts_response.__str__()
    logging.error('Unable to parse contacts: error=%s, data=%s',
                  sys.exc_info().__str__(), contacts_response.__str__())

  return params


def _ParsePortableContacts(response_string):
  contacts = simplejson.loads(response_string)
  out = []
  for entry in contacts['entry']:
    if entry['emails'] and entry['displayName']:
      email = entry['emails'][0]['value']
      display_name = entry['displayName']
      out.append({'email': email, 'name': display_name})
  return out


def _SetNoCache(response):
  """Set no-cache HTTP headers on response."""
  response.headers.add_header('Cache-Control', 'no-cache, must-revalidate')
  response.headers.add_header('Pragma', 'no-cache')
  response.headers.add_header('Expires', 'Fri, 01 Jan 1990 00:00:00 GMT')


def _DisplayContacts(response, contacts_response):
  """Display the user's contacts."""
  path = os.path.join(os.path.dirname(__file__), 'contacts.html')
  output = template.render(path, _BuildTemplateArguments(contacts_response))
  response.out.write(output)


def _DisplayApproved(response, contacts_response):
  """Display a page saying that the user has approved access."""
  path = os.path.join(os.path.dirname(__file__), 'contacts_ok.html')
  output = template.render(path, _BuildTemplateArguments(contacts_response))
  response.out.write(output)


def _DisplayError(response, contacts_response):
  """Display a page showing that an error has occurred."""
  path = os.path.join(os.path.dirname(__file__), 'contacts_error.html')
  output = template.render(path, _BuildTemplateArguments(contacts_response))
  response.out.write(output)


# Map URLs to our RequestHandler classes above
_URLS = [
    ('/sign/poco', FetchContactsHandler),
    ('/sign/poco/callback', FetchContactsCallbackHandler),
]


def main(argv):
  application = webapp.WSGIApplication(_URLS, debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
  main(sys.argv)
